package com.weilele.mvvm.utils.biometric

import android.app.Activity
import android.content.Intent
import android.os.Build
import android.provider.Settings
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import android.util.Base64
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricManager.Authenticators
import androidx.biometric.BiometricPrompt
import androidx.core.content.ContextCompat
import com.weilele.mvvm.MvvmConf
import com.weilele.mvvm.R
import com.weilele.mvvm.adapter.ignoreError
import com.weilele.mvvm.app
import com.weilele.mvvm.base.MvvmActivity
import com.weilele.mvvm.utils.activity.getResString
import com.weilele.mvvm.utils.printStackTrace
import com.weilele.mvvm.utils.result_contract.navigateForResult
import java.security.KeyStore
import javax.crypto.Cipher
import javax.crypto.KeyGenerator
import javax.crypto.SecretKey
import javax.crypto.spec.IvParameterSpec
import kotlin.coroutines.cancellation.CancellationException

/**
 * 开始生物识别
 */
fun AppCompatActivity.startBiometric(
    iv: ByteArray?,/*有值为解密操作，null为加密操作*/
    keyName: String,/*key为别名*/
    authenticators: Int = Authenticators.BIOMETRIC_WEAK,
    dialogInfo: ((BiometricPrompt.PromptInfo.Builder) -> Unit)? = null,/*设置对话框ui*/
    cryptoObjectCall: ((result: BiometricPrompt.AuthenticationResult?, error: Throwable?) -> Unit),/*识别的结果*/
) {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N || !canAuthenticateBiometric(authenticators)) {
        cryptoObjectCall.invoke(
            null,
            UnsupportedOperationException(
                "此设备不支持生物识别（${
                    biometricManager.canAuthenticate(authenticators)
                }）"
            )
        )
    } else {
        requestBiometric(iv, keyName, dialogInfo, cryptoObjectCall)
    }
}

/**
 * 生物识别
 */
private val biometricManager by lazy { BiometricManager.from(app) }

/**
 * 生物识别可用，但是用户没有录入生物识别数据
 * return 是否需要添加指纹（指纹可用，但是没有任何添加）
 */
fun isNeedAddBiometric(authenticators: Int = Authenticators.BIOMETRIC_WEAK): Boolean {
    return biometricManager.canAuthenticate(authenticators) == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED
}

/**
 * 跳转到生物识别添加界面
 */
fun MvvmActivity?.gotoAddBiometric(onResult: (resultCode: Int, intent: Intent?) -> Unit) {
    this ?: return
    val enrollIntent = Intent(Settings.ACTION_BIOMETRIC_ENROLL).apply {
        putExtra(
            Settings.EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED,
            Authenticators.BIOMETRIC_STRONG or Authenticators.DEVICE_CREDENTIAL
        )
    }
    navigateForResult<Activity>(enrollIntent, onResult = onResult)
}

/**
 * 检查是否支持生物识别
 */
fun canAuthenticateBiometric(authenticators: Int = Authenticators.BIOMETRIC_WEAK): Boolean {
    var canAuthenticateBiometric = false
    when (biometricManager.canAuthenticate(authenticators)) {
        BiometricManager.BIOMETRIC_SUCCESS -> {
            //生物识别可用
            canAuthenticateBiometric = true
        }
        BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE -> {
            //用户无法进行身份验证，因为没有合适的硬件（例如，没有生物识别传感器或没有键盘锁）
        }
        BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE -> {
            //由于硬件不可用，用户无法进行身份验证。稍后再试
        }
        BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> {
            //生物识别可用，但是用户没有录入生物识别数据
        }
        BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED -> {
            //用户无法进行身份验证，因为已发现一个或多个硬件传感器存在安全漏洞。在安全更新解决问题之前，受影响的传感器不可用
        }
        BiometricManager.BIOMETRIC_ERROR_UNSUPPORTED -> {
            //用户无法进行身份验证，因为指定的选项与当前的 Android 版本不兼容。
        }
        BiometricManager.BIOMETRIC_STATUS_UNKNOWN -> {
            //无法确定用户是否可以进行身份​​验证。
            // <p>由于与较新的 API 部分不兼容，较旧的 Android 版本可能会返回此状态代码。
            // 希望在受影响设备上启用生物识别身份验证的应用程序在收到此状态代码后仍可调用
            // {@code BiometricPromptauthenticate()}，但应准备好处理可能的错误
        }
    }
    return canAuthenticateBiometric
}

/**
 * 加密数据
 */
fun BiometricPrompt.AuthenticationResult.encrypt(input: String?): String? {
    input ?: return null
    val cipher = this.cryptoObject?.cipher ?: return null
    return ignoreError {
        Base64.encodeToString(
            cipher.doFinal(input.toByteArray()),
            Base64.URL_SAFE
        )
    }
}

/**
 * 解密数据
 */
fun BiometricPrompt.AuthenticationResult.decrypt(input: String?): String? {
    input ?: return null
    val cipher = this.cryptoObject?.cipher ?: return null
    return ignoreError { String(cipher.doFinal(Base64.decode(input, Base64.URL_SAFE))) }
}

/**
 *请求生物识别认证
 */
//@RequiresPermission(
//    allOf = [android.Manifest.permission.USE_BIOMETRIC,
//        android.Manifest.permission.USE_FINGERPRINT]
//)
@RequiresApi(Build.VERSION_CODES.N)
fun AppCompatActivity.requestBiometric(
    iv: ByteArray?,/*有值为解密操作，null为加密操作*/
    keyName: String,/*key为别名*/
    dialogInfo: ((BiometricPrompt.PromptInfo.Builder) -> Unit)? = null,/*设置对话框ui*/
    cryptoObjectCall: ((result: BiometricPrompt.AuthenticationResult?, error: Throwable?) -> Unit),/*识别的结果*/
) {
    try {
        //region 生成生物识别加密对象
        val secretKey = getSecretKey(keyName)
        if (secretKey.second != null) {
            cryptoObjectCall.invoke(null, secretKey.second)
            return
        }
        val cipher = getCipher()
        if (iv == null) {
            cipher.init(Cipher.ENCRYPT_MODE, secretKey.first)
        } else {
            cipher.init(Cipher.DECRYPT_MODE, secretKey.first, IvParameterSpec(iv))
        }
        //endregion
        //region 创建生物识别对话框
        val promptInfo = BiometricPrompt.PromptInfo.Builder()
            .setTitle(app.getResString(R.string.app_name))
            .setNegativeButtonText(app.getResString(android.R.string.cancel))
            .setSubtitle("请求生物识别")
//        .setDescription("132456456498")
            .also {
                dialogInfo?.invoke(it)
            }
            .build()
        //endregion

        //region 创建生物识别对象
        val executor = ContextCompat.getMainExecutor(app)
        val biometricPrompt = BiometricPrompt(this, executor,
            object : BiometricPrompt.AuthenticationCallback() {
                override fun onAuthenticationError(
                    errorCode: Int,
                    errString: CharSequence
                ) {
                    super.onAuthenticationError(errorCode, errString)
                    //点击[setNegativeButtonText]按钮
                    cryptoObjectCall.invoke(null, CancellationException(errString.toString()))
                }

                override fun onAuthenticationSucceeded(
                    result: BiometricPrompt.AuthenticationResult
                ) {
                    super.onAuthenticationSucceeded(result)
                    //认证成功
                    cryptoObjectCall.invoke(result, null)
                }

                override fun onAuthenticationFailed() {
                    super.onAuthenticationFailed()
                    //认证失败
                    cryptoObjectCall.invoke(null, Throwable("验证失败"))
                }
            })
        //endregion

        //region 启动生物识别
        biometricPrompt.authenticate(promptInfo, BiometricPrompt.CryptoObject(cipher))
        //endregion
    } catch (e: Throwable) {
        printStackTrace { e }
        cryptoObjectCall.invoke(null, e)
    }
}

private const val ANDROID_KEY_STORE = "AndroidKeyStore"

@RequiresApi(Build.VERSION_CODES.N)
private fun generateSecretKey(keyName: String): SecretKey {
    val keyGenParameterSpec = KeyGenParameterSpec.Builder(
        keyName,
        KeyProperties.PURPOSE_ENCRYPT
                or KeyProperties.PURPOSE_DECRYPT
    )
        .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
        .setUserAuthenticationRequired(true)
        // Invalidate the keys if the user has registered a new biometric
        // credential, such as a new fingerprint. Can call this method only
        // on Android 7.0 (API level 24) or higher. The variable
        // "invalidatedByBiometricEnrollment" is true by default.
        .setInvalidatedByBiometricEnrollment(true)
        .build()
    val keyGenerator = KeyGenerator.getInstance(
        KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE
    )
    keyGenerator.init(keyGenParameterSpec)
    return keyGenerator.generateKey()
}

@RequiresApi(Build.VERSION_CODES.N)
private fun getSecretKey(keyName: String): Pair<SecretKey?, Throwable?/*由于指纹更改更新，或者删除，导致失败*/> {
    val keyStore = KeyStore.getInstance(ANDROID_KEY_STORE)
    keyStore.load(null)
    return if (keyStore.containsAlias(keyName)) {
        try {
            Pair(keyStore.getKey(keyName, null) as SecretKey, null)
        } catch (e: Throwable) {
            printStackTrace { e }
            ignoreError { keyStore.deleteEntry(keyName) }
            Pair(null, e)
        }
    } else {
        Pair(generateSecretKey(keyName), null)
    }
}

@RequiresApi(Build.VERSION_CODES.M)
private fun getCipher(): Cipher {
    return Cipher.getInstance(
        KeyProperties.KEY_ALGORITHM_AES + "/"
                + KeyProperties.BLOCK_MODE_CBC + "/"
                + KeyProperties.ENCRYPTION_PADDING_PKCS7
    )
}